package proj2501.simple_module;

import java.awt.Graphics;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Random;

import proj2501.geo_util.Gr;
import proj2501.geo_util.LineSegmentD2;
import proj2501.geo_util.VectorD2;
import proj2501.lib.RefreshingPanel;

class P002Snake extends RefreshingPanel {
	private int state = STATE_START;
	private static int STATE_START = 1;
	private static int STATE_GAME = 2;
	private static int STATE_GAME_OVER = 3;
	private long startTime = -1;
	private long startTimeLength = 2000;
	private long startTimeEnd;
	private long gameOverStartTime = -1;
	private long gameOverTimeLength = 8000;
	private long gameOverEndTime;
	
	public P002Snake() {
		setFocusable(true);
		requestFocusInWindow();
		addKeyListener(new KeyAdapter() {
			@Override
			public void keyReleased(KeyEvent e) {
				super.keyReleased(e);
				if (state == STATE_GAME) {
					if (e.getKeyCode() == KeyEvent.VK_RIGHT && snake.velocity.getX() == 0) {
						System.out.println("Going right.");
						snake.velocity = new VectorD2(snake.speed, 0);
						snake.addCurrentPoint();
					} if (e.getKeyCode() == KeyEvent.VK_LEFT && snake.velocity.getX() == 0) {
						System.out.println("Going left.");
						snake.velocity = new VectorD2(-snake.speed, 0);
						snake.addCurrentPoint();
					} if (e.getKeyCode() == KeyEvent.VK_DOWN && snake.velocity.getY() == 0) {
						System.out.println("Going down.");
						snake.velocity = new VectorD2(0, snake.speed);
						snake.addCurrentPoint();
					} if (e.getKeyCode() == KeyEvent.VK_UP && snake.velocity.getY() == 0) {
						System.out.println("Going up.");
						snake.velocity = new VectorD2(0, -snake.speed);
						snake.addCurrentPoint();
					}
				}
			}
		});
	}
	
	private Gr gr;
	@Override
	protected void paintComponent(Graphics g) {
		super.paintComponent(g);
		gr = new Gr(g);
		if (state == STATE_START) {
			if (startTime == -1) {
				startTime = System.currentTimeMillis();
				startTimeEnd = startTime + startTimeLength;
			} else if (startTimeEnd < System.currentTimeMillis()) {
				startTime = -1;
				startStateGame();
			} else {
				long waitTime = startTimeEnd - System.currentTimeMillis();
				g.drawString("Starting Game. " + waitTime, 10, 20);
			}
		} else if (state == STATE_GAME) {
			snake.paint();
			food.paint();
			if (food.checkEat()) {
				snake.length += 50;
				System.out.println("Ate food.");
			}
			g.drawString("Score: " + snake.length, 10, 20);
		} else if (state == STATE_GAME_OVER) {
			snake.paint();
			food.paint();
			if (gameOverStartTime == -1) {
				System.out.println("Game over. (Score: " + snake.length + ")");
				gameOverStartTime = System.currentTimeMillis();
				gameOverEndTime = gameOverStartTime + gameOverTimeLength;
			} else if (gameOverEndTime < System.currentTimeMillis()) {
				gameOverStartTime = -1;
				state = STATE_START;
			} else {
				long waitTime = gameOverEndTime - System.currentTimeMillis();
				g.drawString("Score: " + snake.length, 10, 20);
				g.drawString("Game Over. " + waitTime, 10, 40);
			}
		}
	}
	
	private void startStateGame() {
		state = STATE_GAME;
		snake = new Snake();
		snake.loc = new VectorD2(getWidth() / 2, getHeight() / 2);
		snake.addCurrentPoint();
		snake.velocity = new VectorD2(0, - snake.speed);
		
		food = new Food();
		food.newLocation();
		System.out.println("Game started.");
	}
	
	private Snake snake;
	private class Snake {
		VectorD2 loc;
		double speed = 2;
		VectorD2 velocity;
		double length = 100;
		double lengthTemp;
		LinkedList<VectorD2> points = new LinkedList<VectorD2>();
		LinkedList<LineSegmentD2> lines = new LinkedList<LineSegmentD2>();
		LinkedList<LineSegmentD2> intersectables = new LinkedList<LineSegmentD2>();
		void addCurrentPoint() {
			points.add(loc);
		}
		void paint() {
			lengthTemp = length;
			lines.clear();
			intersectables.clear();
			VectorD2 currentPoint = loc;
			VectorD2 lastPoint = points.getLast();
			LineSegmentD2 line = new LineSegmentD2(currentPoint, lastPoint);
			addLine(line);
			ListIterator<VectorD2> iter = points.listIterator(points.size());
			VectorD2 p1 = iter.previous();
			VectorD2 p2;
			while (iter.hasPrevious()) {
				if (lengthTemp <= 0) {
					break;
				}
				p2 = iter.previous();
				line = new LineSegmentD2(p1, p2);
				addLine(line);
				p1 = p2;
			}
			LineSegmentD2 line1 = lines.getFirst();
			LineSegmentD2 line2 = null;
			if (lines.size() >= 2) {
				line2 = lines.get(1);
			}
			for (LineSegmentD2 line3: lines) {
				if (line3 != line1 && line3 != line2) {
					intersectables.add(line3);
				}
				gr.drawLine(line3);
			}
			double w = getWidth();
			double h = getHeight();
			LineSegmentD2 top = new LineSegmentD2(0, 0, w, 0);
			LineSegmentD2 bottom = new LineSegmentD2(0, h, w, h);
			LineSegmentD2 left = new LineSegmentD2(0, 0, 0, h);
			LineSegmentD2 right = new LineSegmentD2(w, 0, w, h);
			intersectables.add(top);
			intersectables.add(bottom);
			intersectables.add(left);
			intersectables.add(right);
			for (LineSegmentD2 intersectable : intersectables) {
				VectorD2 intersection = line1.getIntersection(intersectable);
				if (intersection != null) {
					gr.drawCircle(intersection, 5);
					if (state != STATE_GAME_OVER) {
						System.out.println("Found collision at: " + intersection);
						state = STATE_GAME_OVER;
					}
				}
			}
			
			if (state == STATE_GAME) {
				loc = loc.add(velocity);
			}
		}
		void addLine(LineSegmentD2 line) {
			double lineLength = line.getLength();
			if (lineLength > lengthTemp) {
				VectorD2 v1 = line.getV1();
				VectorD2 v1ToV2 = line.getV1ToV2().mul(lengthTemp / line.getLength());
				lines.add(new LineSegmentD2(v1, v1.add(v1ToV2)));
				lengthTemp = 0;
			} else {
				lines.add(line);
				lengthTemp -= lineLength;
			}
		}
	}
	private Random random = new Random();
	private Food food;
	private class Food {
		double radius = 10;
		VectorD2 loc;
		public void newLocation() {
			double x = radius + (getWidth() - radius * 2) * random.nextDouble();
			double y = radius + (getHeight() - radius * 2) * random.nextDouble();
			loc = new VectorD2(x, y);
		}
		void paint() {
			gr.drawCircle(loc, radius);
		}
		boolean checkEat() {
			double distance = snake.loc.sub(loc).getLength();
			if (distance <= radius) {
				newLocation();
				return true;
			}
			return false;
		}
	}
}
